<script type="text/javascript" src="essentials.js"></script>
<script type="text/javascript">
var LBUTTON = 0, MBUTTON = 1, RBUTTON = 2;
var LEFT = 'L', RIGHT = 'R', UP = 'U', DOWN = 'D';

/* ports of all opened tabs */
var ports = {};

/* recently closed tabs */
var live_tabs = {
	stack: [],
	tabs: {}
};

var OPTION_URL = chrome.extension.getURL('options.html');

var config, def_config = {
	'trigger': RBUTTON,	// mouse button to trigger stroke
	'timeout': 200,		// a stroke sequence expires after timeout (milliseconds)
	'minstep': 20,		// minimum moving distance of a stroke (in px)
	'categories': {
		'navigation': {
			'to-page-top': [UP],
			'to-page-bottom': [DOWN],
			'history-back': [LEFT],
			'history-forward': [RIGHT],
			'previous-tab': [UP, LEFT],
			'next-tab': [UP, RIGHT]
		},

		'window': {
			'new-window': [UP, DOWN, RIGHT, UP],
			'close-window': [LEFT, DOWN, RIGHT],
			//'minimize-window': [DOWN, LEFT],
			'maximize-window': [RIGHT, UP]
		},

		'tab': {
			'new-tab': [RIGHT, DOWN],
			'close-tab': [LEFT, UP, RIGHT],
			'undo-close-tab': [DOWN, RIGHT],
			'detach-tab': [LEFT, UP],
			'duplicate-tab': [],
			'close-other-tabs': []
		},

		'bookmark': {
			'bookmark-this-page': [DOWN, RIGHT, UP, LEFT],
			'remove-bookmark': [LEFT, DOWN, RIGHT, UP]
		},

		'utilities': {
			'reload': [UP, DOWN],
			'skip-cache-reload': [UP, DOWN, UP],
			'text-zoom-in': [],
			'text-zoom-out': [],
			'text-zoom-reset': [],
			//'search-selected-text': [],
			'view-source': [LEFT, DOWN, RIGHT, DOWN, LEFT],
			'take-screenshot': [RIGHT, LEFT, RIGHT, LEFT]
		},

		'other': {
			'mouse-stroke-options': [DOWN, UP, RIGHT, DOWN]
		}
	}
};

/* stroke handlers */
var actions = {
	'to-page-top': function(tab) {
		tab_exec(tab, 'document.body.scrollTop = 0');
	},
	'to-page-bottom': function(tab) {
		tab_exec(tab, 'document.body.scrollTop = document.body.scrollHeight');
	},
	'history-back': function(tab) {
		tab_exec(tab, 'history.back()');
	},
	'history-forward': function(tab) {
		tab_exec(tab, 'history.forward()');
	},
	'previous-tab': function(tab) {
		chrome.tabs.getAllInWindow(tab.windowId, function(tabs) {
			for (var i = 0; i < tabs.length; ++i) {
				if (tabs[i].id == tab.id) {
					if (!!(tab = tabs[i - 1])) {
						chrome.tabs.update(tab.id, {selected: true});
					}
					break;
				}
			}
		});
	},
	'next-tab': function(tab) {
		chrome.tabs.getAllInWindow(tab.windowId, function(tabs) {
			for (var i = 0; i < tabs.length; ++i) {
				if (tabs[i].id == tab.id) {
					if (!!(tab = tabs[i + 1])) {
						chrome.tabs.update(tab.id, {selected: true});
					}
					break;
				}
			}
		});
	},
	'minimize-window': function(tab) {
//		chrome.windows.getCurrent(function(wnd) {
//			chrome.windows.update(wnd.id, { left: 0, top: 0, width: 0, height: 0 });
//		});
	},
	'maximize-window': function(tab) {
		chrome.windows.getCurrent(function(wnd) {
			chrome.windows.update(wnd.id, { left: 0, top: 0, width: screen.width, height: screen.height });
		});
	},
	'new-window': function(tab) {
		chrome.windows.create();
	},
	'close-window': function(tab) {
		chrome.windows.getCurrent(function(wnd) {
			chrome.windows.remove(wnd.id);
		});
	},
	'new-tab': function(tab) {
		chrome.tabs.create({
			'windowId': tab.windowId,
			'index': tab.index + 1,
			'selected': true
		});
	},
	'close-tab': function(tab) {
		chrome.tabs.remove(tab.id);
	},
	'undo-close-tab': function(tab) {
		if (0 < live_tabs.stack.length) {
			var dead = live_tabs.tabs[live_tabs.stack.pop()];
			chrome.tabs.create({
				'windowId': tab.windowId,
				'index': tab.index + 1,
				'selected': true,
				'url': dead.url
			}, function() {
				/* a new tab is created (same url, different tab id),
				 * the old tab has long gone */
				delete live_tabs.tabs[dead.id];
			});
		}
	},
	'detach-tab': function(tab) {
		chrome.windows.create(null, function(wnd) {
			chrome.tabs.move(tab.id, { windowId: wnd.id, index: 0 });
			/* remove all other tabs */
			chrome.tabs.getAllInWindow(wnd.id, function(tabs) {
				for (var i = 0; i < tabs.length; ++i) {
					if (tabs[i].id != tab.id) {
						chrome.tabs.remove(tabs[i].id);
					}
				}
			});
		});
	},
	'duplicate-tab': function(tab) {
		chrome.tabs.create({
			'windowId': tab.windowId,
			'index': tab.index + 1,
			'url': tab.url
		});
	},
	'close-other-tabs': function(tab) {
		chrome.tabs.getAllInWindow(tab.windowId, function(tabs) {
			for (var i = 0; i < tabs.length; ++i) {
				if (tabs[i].id != tab.id) {
					chrome.tabs.remove(tabs[i].id);
				}
			}
		});
	},
	'bookmark-this-page': function(tab) {
		chrome.bookmarks.getChildren('0', function(root) {
			/* add to bookmarks bar */
			chrome.bookmarks.create({
				parentId: root[0].id,
				title: tab.title || tab.url,
				url: tab.url
			}/*, function(bk) {
				var updt = function(id, info) {
					if ('complete' != info.status) return;
					chrome.tabs.get(id, function(tab) {
						console.log(tab);
						chrome.bookmarks.update(bk.id, { title: tab.title });
						chrome.tabs.onUpdated.removeListener(updt);
					});
				}
				chrome.tabs.onUpdated.addListener(updt);
			}*/);
		});
	},
	'remove-bookmark': function(tab) {
		/* search by hostname, full url sometimes won't return results even
		 * when there are exact matches */
		var host = $create('a', { href: tab.url }).hostname;
		chrome.bookmarks.search(host, function(bks) {
			for (var i = 0; i < bks.length; ++i) {
				if (bks[i].url == tab.url) {
					chrome.bookmarks.remove(bks[i].id);
				}
			}
		});
	},
	'reload': function(tab) {
		tab_exec(tab, 'location.reload()');
	},
	'skip-cache-reload': function(tab) {
		tab_exec(tab, 'location.reload(true)');
	},
	'view-source': function(tab) {
		chrome.tabs.create({
			url: 'view-source:' + tab.url
		});
	},
	'take-screenshot': function(tab) {
		chrome.tabs.captureVisibleTab(null, function(url) {
			chrome.tabs.create({
				'windowId': tab.windowId,
				'index': tab.index + 1,
				'selected': true,
				'url': url
			});
		});
	},
	'text-zoom-in': function(tab) {
		actions['text-zoom-reset'](tab, 0.01);
	},
	'text-zoom-out': function(tab) {
		actions['text-zoom-reset'](tab, -0.01);
	},
	/* the mose UGLY way to zoom text */
	'text-zoom-reset': function(tab, step) {
		if (!live_tabs.tabs[tab.id].zoom || isNaN(step)) {
			live_tabs.tabs[tab.id].zoom = 1;
		}
		var zoom = live_tabs.tabs[tab.id].zoom += isNaN(step) ? 0 : step;
		if (1 == zoom) {
			tab_exec(tab, '\
var sty = document.getElementById("ms-style-' + tab.id + '");\
if (!!sty) document.getElementsByTagName("head")[0].removeChild(sty);\
');
		} else {
			tab_exec(tab, '\
var sty = document.getElementById("ms-style-' + tab.id + '");\
if (!sty) {\
	sty = document.createElement("style");\
	sty.id = "ms-style-' + tab.id + '";\
	sty.type = "text/css";\
	document.getElementsByTagName("head")[0].appendChild(sty);\
}\
sty.innerText = " * { font-size: ' + zoom + 'em !important; } ";\
');
		}
	},
	'search-selected-text': function(tab) {
		/* can't get response from content script yet */
		tab_exec(tab, 'window.getSelection().toString()');
	},
	'mouse-stroke-options': function(tab) {
		chrome.tabs.create({
			url: OPTION_URL
		});
	}
};

/* translate older version configurations to the newest version */
var sync_config = function(config) {
	/* first time run, convert default config object (hard coded) */
	if (config.categories && !config.strokes) {
		/* flat categories */
		config.strokes = {};
		for (k in config.categories) {
			for (k2 in config.categories[k]) {
				config.strokes[k2] = config.categories[k][k2];
			}
		}
	}
	/* from v1.0 to v1.1 */
	if (!config.categories && config.strokes) {
		config.categories = def_config.categories;
		for (k in config.categories) {
			for (k2 in config.categories[k]) {
				config.categories[k][k2] = config.strokes[k2];
			}
		}
	}
	/* synchronize new/removed strokes */
	for (k in def_config.categories) {
		if (!config.categories[k]) {
			config.categories[k] = {};
		}
		for (k2 in def_config.categories[k]) {
			if (!config.strokes[k2]) {
				config.strokes[k2] = [].concat(def_config.categories[k][k2]);
				config.categories[k][k2] = config.strokes[k2];
			}
		}
	}
	for (k in config.categories) {
		for (k2 in config.categories[k]) {
			if (!def_config.categories[k] || !def_config.categories[k][k2]) {
				delete config.categories[k][k2];
				delete config.strokes[k2];
			}
		}
		if (!def_config.categories[k]) {
			delete config.categories[k];
		}
	}
	return config;
}

/* compare stroke sequences */
var match = function(seq1, seq2) {
	if (seq1 === seq2 || !seq1 && !seq2) return true;
	if (!seq1 && seq2 || seq1 && !seq2 || seq1.length != seq2.length) return false;

	return seq1.join(' ') == seq2.join(' ');
}

/* load user config */
var load_config = function(update_tabs) {
	try {
		if (!(config = JSON.parse(localStorage.getItem('config')))) {
			config = def_config;
		}
	} catch (ex) {
		console.error(ex);
		config = def_config;
	}

	sync_config(config);
	localStorage.setItem('config', JSON.stringify(config));

	if (!update_tabs) return;

	/* update config in all tabs */
	chrome.windows.getAll({populate: true}, function(windows) {
		var i, j, tabs;
		for (i = 0; i < windows.length; ++i) {
			tabs = windows[i].tabs;
			for (j = 0; j < tabs.length; ++j) {
				if (ports[tabs[j].id]) {
					ports[tabs[j].id].postMessage(config);
				}
			}
		}
	});
}

/* execute the given script in the given tab */
var tab_exec = function(tab, script)
{
	chrome.tabs.sendRequest(tab.id, script);
}

/* routine to execute when a tab is updated */
chrome.tabs.onUpdated.addListener(function(id, info) {
	if ('complete' !== info.status) return;

	/* create port for broadcast configurations */
	ports[id] = chrome.tabs.connect(id, {name: 'stroke.js-config'});
	ports[id].postMessage(config);

	/* record all opened regular tabs */
	chrome.tabs.get(id, function(tab) {
		if (!tab.url.match(/^chrome/)) {
			live_tabs.tabs[id] = tab;
		}
	});
});

/* routine to execute when a tab is destroied */
chrome.tabs.onRemoved.addListener(function(id) {
	/* remove all ports associated with this tab */
	delete ports[id];

	/* push to the stack of recently closed tabs */
	if (!!live_tabs.tabs[id]) {
		live_tabs.stack.push(id);
		while (10 < live_tabs.stack.length) {
			/* discard tabs closed earlier */
			delete live_tabs.tabs[live_tabs.stack.splice(0, 1)[0]];
		}
	}
});

/* listen to the content script */
chrome.extension.onConnect.addListener(function(port) {
	if ('stroke.html-stroke' != port.name) return;
	/* don't respond to option page */
	if (port.tab.url == OPTION_URL) return;

	port.onMessage.addListener(function(strokes) {
		var i, tab = port.sender.tab;

		/* accept strokes */
		for (id in config.strokes) {
			if (match(strokes, config.strokes[id])) {
				actions[id].call(tab, tab);
				break;
			}
		}
	});
});

/* listen to the config page */
chrome.extension.onConnect.addListener(function(port) {
	if ('stroke.html-config' != port.name) return;

	port.onMessage.addListener(function(command) {
		if ('reload' == command) {
			load_config(true);
		}
	});
});

/* always refresh def_config */
localStorage.setItem('def_config', JSON.stringify(def_config));
/* get user config */
load_config();
</script>